<?php

namespace app\service;


use app\service\tool\FileService;

/**
 * 渲染图片
 * Class RenderImgService
 * @package app\service
 */
final class RenderImgService extends BaseService
{

    /**
     * 实例
     * @var self
     */
    public static $instance;
    /**
     * 渲染引擎
     * @var string
     */
    public $library;
    /**
     * 扩展库列表
     * @var array
     */
    protected static $libraryList = [
        'GD' => 'gd',
        'IMAGICK' => 'imagick'
    ];
    /**
     * 添加文本
     * @var
     */
    public $text;
    /**
     * 宽度
     * @var
     */
    public $width;
    /**
     * 高度
     * @var
     */
    public $height;
    /**
     * 字体颜色
     * @var
     */
    public $backColor;
    /**
     * 字体颜色
     * @var int
     */
    public $foreColor;
    /**
     * 偏移量
     * @var int
     */
    public $offset;
    /**
     * 边距
     * @var int
     */
    public $padding;
    /**
     * 字体文件路径
     * @var
     */
    public $fontFile;
    /**
     * 是否背景透明
     * @var
     */
    public $transparent;

    function __construct()
    {

        parent::__construct();

        // 默认宽度
        $this->width = 100;
        // 默认高度
        $this->height = 100;
        // 默认背景色
        $this->backColor = 0xFFFFFF;
        // 字体颜色
        $this->foreColor = 0x2040A0;
        // 默认偏移量
        $this->offset = -2;
        // 默认边距
        $this->padding = 2;
        // 字体文件路径
        $this->fontFile = \Yii::getAlias('@webroot/fonts/arialbd.ttf');
        // 背景色透明
        $this->transparent = false;

        // 默认渲染引擎
        try {

            // 扩展是空的并且已安装[imagick]
            if (extension_loaded('imagick')) {
                $imagickFormats = (new \Imagick())->queryFormats('PNG');
                if (in_array('PNG', $imagickFormats, true)) {
                    $this->library = self::getLibraryImagick();
                }
            }
            // 扩展是空的并且已安装[gd]
            if (extension_loaded('gd')) {
                $gdInfo = gd_info();
                if (!empty($gdInfo['FreeType Support'])) {
                    $this->library = self::getLibraryGd();
                }
            }

        } catch (\Exception $error) {
            $this->library = self::getLibraryGd();
        }

    }

    public function rules()
    {

        // 渲染引擎列表
        $libraryList = array_values(self::getLibraryList());
        return [
            [['width', 'height', 'fontFile', 'text', 'library'], 'required'],
            [['width', 'height', 'offset', 'padding'], 'number'],
            [['fontFile', 'text'], 'string'],
            ['library', 'in', 'range' => $libraryList, 'message' => '渲染引擎不合法'],
            [['backColor', 'foreColor'], 'checkColor16'],
            ['transparent', 'boolean']
        ];
    }

    public function attributeLabels()
    {
        return [
            'library' => '渲染引擎',
            'text' => '渲染文字',
            'width' => '渲染图宽度',
            'height' => '渲染图高度'
        ];
    }

    /**
     * 检测字体颜色是否为16进制
     * @param null $attributeNames
     * @param bool $clearErrors
     * @return bool
     */
    public function checkColor16($attributeNames = null, $clearErrors = true)
    {
        return ctype_xdigit($this->$attributeNames);
    }

    /**
     * 加载整体[Model]
     * @param array $data 初始化的数据
     * @return self
     */
    public static function loadModel($data = [])
    {

        return self::getInstance()->initInstance($data);
    }

    /**
     * 获取实例
     * @return self
     */
    protected static function getInstance()
    {

        // 实例不存在初始化
        if (empty(self::$instance)) self::$instance = new self();

        // 存在直接返回
        return self::$instance;
    }

    /**
     * 初始化一些默认信息
     * @param array $_data
     * @return self
     */
    protected function initInstance($_data = [])
    {

        // 获取实例
        $instance = self::getInstance();

        // ******** 做一些参数的赋值 ********
        foreach ($_data as $k => $v) {

            if (empty($_data) || !$this->hasAttribute($k)) {
                unset($_data[$k]);
            }
        }

        $instance->setAttributes($_data);

        // 返回
        return $instance;
    }

    /**
     * @return bool
     */
    public function renderImg()
    {

        // 有错误或者验证失败
        if ($this->hasErrors()||!$this->validate()) {return false;}

        try {

            // 检测渲染库执行渲染
            switch ($this->library) {
                // imagick库
                case self::getLibraryImagick():
                    return $this->renderFromImagick();
                    break;
                // GD库
                case self::getLibraryGd():
                    return $this->renderFromGd();
                    break;
            }

            // 返回否
            return false;

        } catch (\Exception $error) {

            // 记录下错误日志
            \Yii::error([

                "``````````````````````````````````````````````````````````",
                "``                      渲染图片失败                      ``",
                "`` 错误详情: {$error->getMessage()}                      ``",
                "`` 错误信息和参数详情:                                     ``",
                "````````````````````````````````````````````````````````",
                $error->getTraceAsString()
            ], 'error');

            return false;
        }
    }

    /**
     * 以[GD]库渲染图片
     * @return false|string
     */
    protected function renderFromGd() {

        $image = imagecreatetruecolor($this->width, $this->height);

        $backColor = imagecolorallocate(
            $image,
            (int) ($this->backColor % 0x1000000 / 0x10000),
            (int) ($this->backColor % 0x10000 / 0x100),
            $this->backColor % 0x100
        );
        imagefilledrectangle($image, 0, 0, $this->width - 1, $this->height - 1, $backColor);
        imagecolordeallocate($image, $backColor);

        // 是否透明色
        if ($this->transparent) {
            imagecolortransparent($image, $backColor);
        }

        $foreColor = imagecolorallocate(
            $image,
            (int) ($this->foreColor % 0x1000000 / 0x10000),
            (int) ($this->foreColor % 0x10000 / 0x100),
            $this->foreColor % 0x100
        );

        $length = strlen($this->text);
        $box = imagettfbbox(30, 0, $this->fontFile, $this->text);
        $w = $box[4] - $box[0] + $this->offset * ($length - 1);
        $h = $box[1] - $box[5];
        $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
        $x = 10;
        $y = round($this->height * 27 / 40);
        for ($i = 0; $i < $length; ++$i) {
            $fontSize = (int) (mt_rand(26, 32) * $scale * 0.8);
            $angle = mt_rand(-10, 10);
            $letter = $this->text[$i];
            $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter);
            $x = $box[2] + $this->offset;
        }

        imagecolordeallocate($image, $foreColor);

        ob_start();
        imagepng($image);
        imagedestroy($image);

        return ob_get_clean();
    }

    /**
     * 以[Imagick]库渲染图片
     * @return false|string
     */
    protected function renderFromImagick() {

        $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . str_pad(dechex($this->backColor), 6, 0, STR_PAD_LEFT));
        $foreColor = new \ImagickPixel('#' . str_pad(dechex($this->foreColor), 6, 0, STR_PAD_LEFT));

        $image = new \Imagick();
        $image->newImage($this->width, $this->height, $backColor);

        $draw = new \ImagickDraw();
        $draw->setFont($this->fontFile);
        $draw->setFontSize(30);
        $fontMetrics = $image->queryFontMetrics($draw, $this->text);

        $length = strlen($this->text);
        $w = (int) $fontMetrics['textWidth'] - 8 + $this->offset * ($length - 1);
        $h = (int) $fontMetrics['textHeight'] - 8;
        $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
        $x = 10;
        $y = round($this->height * 27 / 40);
        for ($i = 0; $i < $length; ++$i) {
            $draw = new \ImagickDraw();
            $draw->setFont($this->fontFile);
            $draw->setFontSize((int) (mt_rand(26, 32) * $scale * 0.8));
            $draw->setFillColor($foreColor);
            $image->annotateImage($draw, $x, $y, mt_rand(-10, 10), $this->text[$i]);
            $fontMetrics = $image->queryFontMetrics($draw, $this->text[$i]);
            $x += (int) $fontMetrics['textWidth'] + $this->offset;
        }

        $image->setImageFormat('png');
        return $image->getImageBlob();
    }

    /**
     * 获取最终数据
     * @param string $key | 必须传输字符串
     * @return array|mixed|null
     */
    public function getResult($key = '')
    {

        // 结果非数组返回下
        if (!empty($this->responseData[$key])) {

            // 如果有 地址加又拍云地址
            if (!empty($this->responseData['url']) &&
                !strstr($this->responseData['url'], $this->domain))
                $this->responseData['url'] = FileService::addImgDomain($this->responseData['url'],
                    null, '');

            return $this->responseData[$key];
        }

        $end = [];
        foreach ($this->responseData as $k => $v) {

            if ($key == 'url' && !empty($v[$key]) && !strstr($v[$key], $this->domain))
                $v[$key] = FileService::addImgDomain($v[$key], null, '');;

            if (!empty($v[$key])) {
                $end[$k] = $v[$key];
            } else {
                $end[$k] = $v;
            }
        }

        return $end;
    }

    /**
     * 获取[渲染库][GD]值
     * @return mixed
     */
    public static function getLibraryGd() {
        return self::$libraryList['GD'];
    }
    /**
     * 获取[渲染库][GD]值
     * @return mixed
     */
    public static function getLibraryImagick() {
        return self::$libraryList['IMAGICK'];
    }
    /**
     * 获取[渲染库]列表值
     * @return mixed
     */
    public static function getLibraryList() {
        return self::$libraryList;
    }
}
